/* libfake437 - a library for the rendering of code page 437 `graphics'
 * Copyright (C) 2008, 2009, 2010 Jack Kelly <endgame.dos@gmail.com>
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include <assert.h>
#include <stdlib.h>
#include <SDL.h>
#include "fake437/types.h"
#include "fake437/primitive.h"
#include "fake437/surface.h"
#include "fake437/display.h"

#define INITIAL_SIZE 4
static const F437DisplayCell blank = { F437_COLOR_BLACK, F437_COLOR_BLACK, 0 };

F437DisplayLayer* f437_display_layer_new(int left, int top,
                                         int width, int height) {
  F437DisplayLayer* result = f437_display_layer_try_new(left, top,
                                                        width, height);
  if (result == NULL) abort();
  return result;
}

F437DisplayLayer* f437_display_layer_try_new(int left, int top,
                                             int width, int height) {
  F437DisplayLayer* result;
  int i;
  assert(width > 0);
  assert(height > 0);
  result = malloc(sizeof(*result));
  if (result == NULL) return result;
  result->refcount = 1;
  result->left = left;
  result->top = top;
  result->width = width;
  result->height = height;
  result->cells = malloc(sizeof(*result->cells) * width * height);
  if (result->cells == NULL) {
    free(result);
    return NULL;
  }
  for (i = 0 ; i < width * height ; i++) result->cells[i] = blank;
  return result;
}

F437DisplayLayer* f437_display_layer_ref(F437DisplayLayer* layer) {
  assert(layer != NULL);
  layer->refcount++;
  return layer;
}

void f437_display_layer_unref(F437DisplayLayer* layer) {
  assert(layer != NULL);
  layer->refcount--;
  if (layer->refcount == 0) {
    free(layer->cells);
    free(layer);
  }
}

F437Display* f437_display_new(SDL_Surface* surface,
                              const F437Font* font) {
  F437Display* result = f437_display_try_new(surface, font);
  if (result == NULL) abort();
  return result;
}

F437Display* f437_display_try_new(SDL_Surface* surface,
                                  const F437Font* font) {
  F437Display* result;
  int i;
  assert(surface != NULL);
  result = malloc(sizeof(*result));
  if (result == NULL) return NULL;
  result->surface = f437_surface_new(surface, font);
  result->size = 0;
  result->allocated_size = INITIAL_SIZE;
  result->layers = malloc(sizeof(*result->layers) * INITIAL_SIZE);
  if (result->layers == NULL) {
    free(result);
    return NULL;
  }
  for (i = 0 ; i < INITIAL_SIZE ; i++) result->layers[i] = NULL;
  return result;
}

SDL_Surface* f437_display_free(F437Display* display) {
  SDL_Surface* result;
  int i;
  assert(display != NULL);
  result = f437_surface_free(display->surface);
  for (i = 0 ; i < display->size ; i++) {
    f437_display_layer_unref(display->layers[i]);
  }
  free(display->layers);
  free(display);
  return result;
}

void f437_display_insert_layer(F437Display* display, F437DisplayLayer* layer,
                               int n) {
  int i;
  assert(display != NULL);
  assert(layer != NULL);
  assert(n >= 0 && n <= display->size);

  /* Increase allocated size if necessary */
  if (display->size == display->allocated_size) {
    display->allocated_size *= 2;
    display->layers = realloc(display->layers,
                              sizeof(*display->layers)
                              * display->allocated_size);
    if (display->layers == NULL) abort();
  }
  display->size++;

  /* Make room for the new layer */
  for (i = display->size - 1 ; i > n ; i--) {
    display->layers[i] = display->layers[i - 1];
  }

  display->layers[n] = f437_display_layer_ref(layer);
}

F437DisplayLayer* f437_display_get_layer(F437Display* display, int n) {
  assert(display != NULL);
  assert(n >= 0 && n < display->size);
  return f437_display_layer_ref(display->layers[n]);
}

void f437_display_remove_layer(F437Display* display, int n) {
  int i;
  assert(display != NULL);
  assert(n >= 0 && n < display->size);

  /* Remove the layer */
  f437_display_layer_unref(display->layers[n]);
  display->size--;
  for (i = n ; i < display->size; i++) {
    display->layers[i] = display->layers[i + 1];
  }

  /* Shrink the allocated space if we're underusing it */
  if (display->size < display->allocated_size / 3 &&
      display->allocated_size > INITIAL_SIZE) {
    display->allocated_size /= 2;
    display->layers = realloc(display->layers,
                              sizeof(*display->layers)
                              * display->allocated_size);
    if (display->layers == NULL) abort();
  }
}

void f437_display_swap_layers(F437Display* display, int n, int m) {
  F437DisplayLayer* temp;
  assert(display != NULL);
  assert(n >= 0 && n < display->size);
  assert(m >= 0 && m < display->size);
  temp = display->layers[n];
  display->layers[n] = display->layers[m];
  display->layers[m] = temp;
}

void f437_display_push_layer(F437Display* display, F437DisplayLayer* layer) {
  assert(display != NULL);
  f437_display_insert_layer(display, layer, display->size);
}

F437DisplayLayer* f437_display_peek_layer(F437Display* display) {
  assert(display != NULL);
  return f437_display_get_layer(display, display->size - 1);
}

void f437_display_pop_layer(F437Display* display) {
  assert(display != NULL);
  f437_display_remove_layer(display, display->size - 1);
}

void f437_display_draw(F437Display* display) {
  F437DisplayLayer* layer;
  F437DisplayCell* cell;
  int i;
  int x;
  int xx;
  int y;
  int yy;
  assert(display != NULL);
  SDL_FillRect(display->surface->surface, NULL,
               SDL_MapRGB(display->surface->surface->format, 0, 0, 0));
  if (SDL_MUSTLOCK(display->surface->surface)) {
    SDL_LockSurface(display->surface->surface);
  }
  for (i = 0 ; i < display->size ; i++) {
    layer = display->layers[i];
    for (y = 0 ; y < layer->height ; y++) {
      for (x = 0 ; x < layer->width ; x++) {
        xx = x + layer->left;
        yy = y + layer->top;
        if (xx < 0 || xx >= display->surface->width ||
            yy < 0 || yy >= display->surface->height) {
          continue;
        }
        cell = &layer->cells[y * layer->width + x];
        f437_surface_put_char(display->surface, xx, yy, cell->ch,
                              f437_get_color(cell->fg),
                              f437_get_color(cell->bg));
      }
    }
  }
  if (SDL_MUSTLOCK(display->surface->surface)) {
    SDL_UnlockSurface(display->surface->surface);
  }
}
